﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using System.Globalization;
using Microsoft.SharePoint.Publishing;
using System.IO;

namespace SharePoint.Contrib.Extensions
{
    /// <summary>
    /// Extension methods for the SPWeb class
    /// </summary>
    public static class SPWebExtensions
    {

        /// <summary>
        /// Runs a piece of code with unsafe updates set to true and then returns to the previous value.
        /// </summary>
        /// <param name="action">The code (delegate) to run</param>
        public static void RunWithUnsafeUpdates(this SPWeb instance, Action<SPWeb> action)
        {
            bool beforeState = instance.AllowUnsafeUpdates;

            try
            {
                if (instance.AllowUnsafeUpdates == false)
                {
                    instance.AllowUnsafeUpdates = true;
                }

                action(instance);
            }
            finally
            {
                instance.AllowUnsafeUpdates = beforeState;
            }
        }
         
        /// <summary>
        /// Tries to find a list within the SPWeb using the name (internal name). A return value indicates whether it succeeded or not.
        /// </summary>
        /// <param name="name">The internal name of the list (aka RootFolder.Name)</param>
        public static bool TryFindListByName(this SPWeb instance, string name, out SPList list)
        {
            if (instance.Lists.Contains(name) == false)
            {
                list = null;
                return false;
            }

            list = instance.FindListByName(name);
            return true;
        }

        /// <summary>
        /// Searches for a list within the SPWeb using the name (internal name) of the list.
        /// </summary>
        /// <param name="name">The internal name of the list (aka RootFolder.Name)</param>
        /// <exception cref="System.ArgumentException">Is thrown is the list is not found.</exception>
        public static SPList FindListByName(this SPWeb instance, string name)
        {
            SPList list = null;
            string key = name;

            if (instance.Properties.ContainsKey(key))
            {
                try
                {
                    list = list.Lists[new Guid(instance.Properties[key])];
                }
                catch(NullReferenceException)
                {
                    SPSecurity.RunWithElevatedPrivileges(() =>
                    {
                        using (var sitecollection = new SPSite(instance.Site.ID))
                        {
                            using (var site = sitecollection.OpenWeb(instance.ID))
                            {
                                instance.Properties.Remove(key);
                                instance.Properties.Update();
                            }
                        }
                    });

                    return FindListByName(instance, name);
                }
            }
            else
            {
                // RootFolder == internal name, loop through and find (null will be returned if its not found)
                list = (from SPList spl in instance.Lists
                        where spl.Name() == name
                        select spl).FirstOrDefault();

                if (list == null)
                {
                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
                        "Failed to find SPList named '{0}' on web '{1}'.",
                        name, 
                        instance.Url));
                }

                SPSecurity.RunWithElevatedPrivileges(() =>
                {
                    using (var sitecollection = new SPSite(instance.Site.ID))
                    {
                        using (var site = sitecollection.OpenWeb(instance.ID))
                        {
                            instance.Properties.Add(key, list.ID.ToString("B"));
                            instance.Properties.Update();
                        }
                    }
                });



            }

            return list;
        }

        /// <summary>
        /// Searches for a list template within the SPWeb using the name (internal name) of the list template.
        /// </summary>
        /// <param name="name">The internal name of the template</param>
        public static SPListTemplate FindTemplateByInternalName(this SPWeb instance, string name)
        {
            var query = from SPListTemplate template in instance.ListTemplates
                        where template.InternalName.Equals(name, StringComparison.OrdinalIgnoreCase)
                        select template;

            return query.FirstOrDefault();
        }

        /// <summary>
        /// Creates an instance of the LibraryBuilder class.
        /// </summary>
        /// <param name="name">The name (internal name) of the library to create</param>
        public static LibraryBuilder CreateLibrary(this SPWeb instance, string name)
        {
            return LibraryBuilder.Create(instance, name);
        }

        /// <summary>
        /// Determines whether the specified SPWeb contains a content type.
        /// </summary>
        /// <param name="contentTypeId">The id of the content type</param>
        public static bool ContainsContentType(this SPWeb instance, SPContentTypeId id)
        {
            SPContentType result;
            return instance.TryFindContentType(id, out result);
        }

        /// <summary>
        /// Determines whether the specified SPWeb contains a content type and returns that content type if it does.
        /// </summary>
        /// <param name="contentTypeId">The id of the content type</param>
        public static bool TryFindContentType(this SPWeb instance, SPContentTypeId id, out SPContentType result)
        {
            SPContentTypeId match = instance.ContentTypes.BestMatch(id);

            if (match.IsChildOf(id))
            {
                result = instance.ContentTypes[match];
                return true;
            }

            result = default(SPContentType);
            return false;
        }

        /// <summary>
        /// Adds a new page to the pageslist.
        /// </summary>
        /// <param name="layout">Name of pagelayout (the file)</param>
        /// <param name="page">Name of page (aspx file)</param>
        /// <param name="title">Title of page</param>
        /// <returns>The url of the file</returns>
        public static string CreatePublishingPage(this SPWeb instance, string layout, string page, string title)
        {
            PublishingSite ps = new PublishingSite(instance.Site);
            page = Path.GetFileNameWithoutExtension(page);
            string url = string.Empty;

            try
            {
                var file = instance.FindMasterPageCatalogFile(layout);
                PageLayout pl = ps.PageLayouts[file.Url];

                var pw = ps.RootWeb.EnsurePublishingWeb();
                var pages = pw.GetPublishingPages();
                var aspx = string.Concat(page, ".aspx");

                var pp = (from item in pages
                          where item.Name == aspx
                          select item).FirstOrDefault();

                //ensure that the page isn't present yet (in case the feature was activated and deactivated) or that the layout is correct
                if (pp == null)
                {

                    pp = pw.GetPublishingPages().Add(aspx, pl);
                    pp.Title = title;
                    pp.Update();
                    pp.CheckIn("Added new page: " + aspx);

                }

                url = pp.Url;

            }
            catch (Exception e)
            {
                throw new Exception(string.Format("Failed to create a new page in the pages list at {0}.", ps.Site.ServerRelativeUrl), e);
            }
            finally
            {
                // no dispose (using the SPWeb sent in)?
                //ps.Site.Dispose();
                //ps.RootWeb.Dispose();
            }

            return url;
        }

        /// <summary>
        /// Ensures that the SPWeb is a publishing web and returns that instance
        /// </summary>
        public static PublishingWeb EnsurePublishingWeb(this SPWeb instance)
        {
            if (PublishingWeb.IsPublishingWeb(instance))
            {
                // Get the publishingweb                  
                return PublishingWeb.GetPublishingWeb(instance);
            }
            else
            {
                throw new Exception(string.Format("The web '{0}' is not a PublishingWeb.", instance.Url));
            }
        }

        /// <summary>
        /// Finds a file in the _catalogs/masterpage directory.
        /// </summary>
        /// <param name="fileName">Name of the file</param>
        public static SPFile FindMasterPageCatalogFile(this SPWeb instance, string fileName)
        {
            SPList masterpages = instance.GetCatalog(SPListTemplateType.MasterPageCatalog);

            fileName = Path.GetFileNameWithoutExtension(fileName);

            var file = (from SPListItem item in masterpages.Items
                        where Path.GetFileNameWithoutExtension(item.File.Name) == fileName
                        select item.File).FirstOrDefault();


            if (file == null)
            {
                throw new Exception(string.Format("No file named '{0}' was found.", fileName));
            }

            return file;
        }

        /// <summary>
        /// Changes the site masterpage for the web.
        /// </summary>
        /// <param name="master">The name of the masterpage (ex default)</param>
        public static void ChangeSiteMasterPage(this SPWeb instance, string master)
        {
            var file = FindMasterPageCatalogFile(instance, master);

            if (file == null)
            {
                throw new Exception(string.Format("No masterpage named '{0}' was found.", master));
            }

            instance.RunWithUnsafeUpdates(web =>
            {
                instance.CustomMasterUrl = file.ServerRelativeUrl;
                instance.Update();
            });
        }

        /// <summary>
        /// Defaults the available pagelayouts on the SPWeb to not include any pagelayouts, aka resets them.
        /// </summary>
        public static void ResetAvailablePageLayouts(this SPWeb instance)
        {
            var web = EnsurePublishingWeb(instance);

            web.SetAvailablePageLayouts(new PageLayout[0], false);
            web.Update();
        }

        /// <summary>
        /// Removes/Hides all the content types from the pages list.
        /// </summary>
        public static void ResetAvailableContentTypesOnPagesList(this SPWeb instance)
        {

            var web = EnsurePublishingWeb(instance);

            SPList pages = web.PagesList;
            pages.ResetAvailableContentTypes();

        }

    }
}
